package org.marsdonne.auth.restful;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.marsdonne.archives.Client;
import org.marsdonne.archives.MessagePushBindingRequest;
import org.marsdonne.archives.OrganizationMessagePushMgrServ;
import org.marsdonne.archives.Platform;
import org.marsdonne.auth.EndPointUser;
import org.marsdonne.auth.EndPointUserRepos;
import org.marsdonne.auth.PlatformUser;
import org.marsdonne.auth.UnionUser;
import org.marsdonne.auth.wechat.WechatAuthApi;
import org.marsdonne.commons.wechat.AccessTokenServ;
import org.marsdonne.messages.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 微信公众号（Wechat Media Platform）服务器接口
 * 仅用于公众号用户的关注/取关的简易处理,不处理除用户关注/取关以外的其它事宜
 * 若后续需要集成其它事宜处置时，需将该接口移至专用的“统一代理入口”服务后，再通过异步嫁接的方式将用户关注/取关事宜路由回本服务的全新实现。
 */
@RestController
public class WmpController {
    private static final Logger logger = LoggerFactory.getLogger(WmpController.class);
    private final static SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");

    @Resource
    private AccessTokenServ accessTokenServ;

    @Resource
    private EndPointUserRepos endPointUserRepos;

    @Resource
    private WechatAuthApi wechatAuthApi;

    @Resource
    private MessageServ messageServ;

    @Resource
    private OrganizationMessagePushMgrServ organizationMessagePushMgrServ;

    @GetMapping("/wmp")
    public void verify(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        request.setCharacterEncoding("UTF-8");
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
        logger.info("signature[{}], timestamp[{}], nonce[{}], echostr[{}]", signature, timestamp, nonce, echostr);
        try (PrintWriter resp = response.getWriter()) {
            if (signature != null && timestamp != null && nonce != null && CheckUtil.checkSignature(signature, timestamp, nonce))
                resp.write(echostr);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    @PostMapping("/wmp")
    @Transactional
    public void subscribe(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        logger.info("WmpController#subscribe");
        request.setCharacterEncoding("UTF-8");
        Map<String, String> parsedContent = parseXml(request);
        String msgType = parsedContent.get("MsgType");
        String fromUserName = parsedContent.get("FromUserName");
        String toUserName = parsedContent.get("ToUserName");
        switch (msgType) {
            case "event"://处理关注和取关
                String event = parsedContent.get("Event");
                logger.info("msgType[{}], event[{}], fromUserName[{}], toUserName[{}]", msgType, event, fromUserName, toUserName);
                if("subscribe".equals(event)) {
                    //绑定公众号端的用户信息
                    EndPointUser endPointUser = this.endPointUserRepos.bind(Client.E, Platform.Wechat, fromUserName, toUserName, fromUserName);//昵称和密码临时占位，后续生成随机码
                    //获取微信平台的统一用户标识UnionID
                    String token = this.accessTokenServ.getToken(Client.E).get(AccessTokenServ.Resp4Token.AccessToken.value).toString();
                    Map<String, String> wechatResp = this.wechatAuthApi.getUserInfo(token, fromUserName);
                    //使用UnionID匹配并关联平台用户信息
                    String platformId = wechatResp.get(WechatAuthApi.Resp4UserInfo.UnionID.value);
                    if (platformId != null) {
                        PlatformUser platformUser = this.endPointUserRepos.bind(endPointUser, Platform.Wechat, platformId, null, null);

                        UnionUser unionUser = platformUser.getUnionUser();
                        if(unionUser != null && unionUser.getOrganizationLicenseCode() != null) {//加载用户绑定的商户信息
                            String message_push_identity = endPointUser.getIdentifier();
                            String licenseCode = unionUser.getOrganizationLicenseCode();
                            //匹配绑定的商户端用户信息
                            Optional<EndPointUser> optionalEndPointUser4ClientC = this.endPointUserRepos.findAllByUnionUser(unionUser)
                                    .stream().filter(endPointUser4UnionUser -> Client.C.equals(endPointUser4UnionUser.getClient())).findFirst();
                            if(optionalEndPointUser4ClientC.isPresent()) {
                                EndPointUser endPointUser4ClientC = optionalEndPointUser4ClientC.get();
                                //推送公众号消息，反馈用户绑定成功
                                UserLinkedMessage message = new UserLinkedMessage(Client.E, fromUserName);
                                message.setContents(new HashMap<NoticeUserLinked, String>() {{
                                    put(NoticeUserLinked.Desc, "您好，您的公众号已成功关联。");
                                    put(NoticeUserLinked.CurrentUser, endPointUser4ClientC.getNickname());
                                    put(NoticeUserLinked.LinkedUser, unionUser.getRealName());
                                    put(NoticeUserLinked.DateLinked, dateTimeFormat.format(new Date()));
                                    put(NoticeUserLinked.Remark, "祝您后续使用顺利愉快");
                                }});
                                this.messageServ.send(message);

                                //将“商户负责人绑定指令”，转发至档案服务并尤其处理
                                String username = endPointUser4ClientC.getIdentifier();
                                this.organizationMessagePushMgrServ.bind(new MessagePushBindingRequest(
                                        message_push_identity, licenseCode, username
                                ));
                            }
                        }
                    }
                } else if("unsubscribe".equals(event)) {
                    //ignore
                }
                break;
            case "text"://忽略
                String content = parsedContent.get("Content");
                logger.info("msgType[{}], content[{}], fromUserName[{}], toUserName[{}]", msgType, content, fromUserName, toUserName);
                break;
        }
    }

    private static Map<String, String> parseXml(HttpServletRequest request) {
        Map<String, String> map = new LinkedHashMap<>();

        try (InputStream inputStream = request.getInputStream()) {
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(inputStream);
            Element root = document.getRootElement();
            List<Element> elementList = root.elements();
            for (Element element : elementList)
                map.put(element.getName(), element.getText());
        } catch (IOException | DocumentException exception) {
            exception.printStackTrace();
        }

        return map;
    }

    private static class CheckUtil {
        private static final String token = "lesexia190509";

        static boolean checkSignature(String signature, String timestamp, String nonce) {
            String[] str = new String[]{token, timestamp, nonce};
            Arrays.sort(str);
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < str.length; i++) {
                builder.append(str[i]);
            }
            String temp = SHA1.encode(builder.toString());
            return signature.equals(temp);
        }
    }

    private static class SHA1 {
        private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

        private static String getFormattedText(byte[] bytes) {
            int length = bytes.length;
            StringBuilder builder = new StringBuilder(length * 2);
            for (int i = 0; i < length; i++) {
                builder.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
                builder.append(HEX_DIGITS[bytes[i] & 0x0f]);
            }
            return builder.toString();
        }

        public static String encode(String str) {
            if (str == null)
                return null;
            try {
                MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
                messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
                return getFormattedText(messageDigest.digest());
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
